<?php

namespace Yxy\YWechat;

use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Kernel\Exceptions\RuntimeException;
use EasyWeChat\Pay\Application;
use Exception;
use EasyWeChat\Pay\Message;
use Psr\Http\Message\ResponseInterface;
use ReflectionException;
use Throwable;

/**
 * 微信支付
 */
class Pay
{
    /**
     * app
     * @var Application
     */
    protected static Application $app;
    /**
     * 配置信息
     * @var array
     */
    protected static array $config;
    protected static array $errConfig = [
        "SYSTEM_ERROR" => "系统错误",
        "APPID_MCHID_NOT_MATCH" => "商户号和appid没有绑定关系",
        "PARAM_ERROR" => "参数错误",
        "INVALID_REQUEST" => "请求参数符合参数格式，但不符合业务规则",
        "NO_AUTH" => "商户信息不合法",
        "NOT_ENOUGH" => "资金不足",
        "ACCOUNTERROR" => "商户账户付款受限",
        "QUOTA_EXCEED" => "超出商户单日转账额度",
        "FREQUENCY_LIMITED" => "频率超限",
    ];

    /**
     * 初始化支付
     * @return void
     * @throws InvalidArgumentException
     */
    protected static function initApp(): void
    {
        $config = config('ywechat.pay');
        foreach ($config as $value) {
            if (empty($value)) {
                throw new Exception("请先填写微信支付配置");
            }
        }
        self::$config = $config;
        self::$app = new Application([
            'mch_id' => $config['mch_id'],
            // 商户证书
            'private_key' => $config['private_key'],
            'certificate' => $config['certificate'],
            // v3 API 秘钥
            'secret_key' => $config['secret_key'],
            // v2 API 秘钥
            'v2_secret_key' => $config['v2_secret_key'],
            // v3平台证书
            'platform_certs' => [
                $config['platform_certs'],
            ],
            'http' => [
                'throw' => true,
            ],
        ]);
    }

    /**
     * 获取支付app
     * @return Application
     * @throws InvalidArgumentException
     */
    public static function getApp(): Application
    {
        if (empty(self::$app)) {
            self::initApp();
        }
        return self::$app;
    }

    /**
     * 获取支付信息
     * @param string $order_no 订单号
     * @param float $total 钱数(不用乘100)
     * @param string $open_id 用户openid
     * @param string $description 描述
     * @param string $notify 回调地址
     * @param string $signType 加密方法,默认RSA,V2要传MD5
     * @return array 微信支付信息
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     * @throws Exception
     */
    public static function payData(string $order_no, float $total, string $open_id, string $description, string $notify, string $signType = "RSA"): array
    {
        $app = self::getApp();
        $response = $app->getClient()->postJson("v3/pay/transactions/jsapi", [
            "mchid" => self::$config['mch_id'],
            "out_trade_no" => $order_no,
            "appid" => self::$config['appid'],
            "description" => $description,
            "notify_url" => $notify,
            "amount" => [
                "total" => (int)bcmul((string)$total, '100'),
                "currency" => "CNY",
            ],
            "payer" => [
                "openid" => $open_id,
            ],
        ]);
        $res = $response->toArray();

        if (empty($res['prepay_id'])) {
            throw new Exception($res['message']);
        }

        $prepayId = $res['prepay_id'];
        $utils = $app->getUtils();
        $appId = self::$config['appid'];
        return $utils->buildBridgeConfig($prepayId, $appId, $signType);
    }

    /**
     * 支付回调
     * @param callable $callback function($wechatOrderNo(微信订单号), $orderNo(商户订单号))
     * @return ResponseInterface
     * @throws InvalidArgumentException
     * @throws RuntimeException
     * @throws ReflectionException
     * @throws Throwable
     */
    public static function payNotify(callable $callback): ResponseInterface
    {
        $app = self::getApp();
        $server = $app->getServer();
        $server->handlePaid(function (Message $message, \Closure $next) use ($app, $callback) {
            // 微信订单号
            $wechatOrderNo = $message->transaction_id;
            // 商户订单号
            $orderNo = $message->out_trade_no;
            // 验证订单是否支付成功
            $response = $app->getClient()->get("v3/pay/transactions/out-trade-no/{$message->out_trade_no}", [
                'query' => [
                    'mchid' => $app->getMerchant()->getMerchantId(),
                ],
            ])->toArray();
            if ($response['trade_state'] == 'SUCCESS') {
                $callback($wechatOrderNo, $orderNo);
            }
            return $next($message);
        });

        return $server->serve();
    }

    /**
     * 微信退款
     * @param string $wechat_order 微信订单号
     * @param string $out_refund_no 退款单号
     * @param float $refund 退款金额
     * @param float $total 订单总金额
     * @param string $notify 回调地址
     * @return mixed
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     */
    public static function refund(string $wechat_order, string $out_refund_no, float $refund, float $total, string $notify): mixed
    {
        return self::getApp()->getClient()->postJson('v3/refund/domestic/refunds', [
            'transaction_id' => $wechat_order,
            'out_refund_no' => $out_refund_no,
            'notify_url' => $notify,
            'amount' => [
                'refund' => $refund,
                'total' => (int)bcmul((string)$total, '100'),
                'currency' => 'CNY',
            ],
        ]);
    }

    /**
     * 退款回调
     * @param callable $callback function($wechatOrderNo(微信订单号),$refundNo(商户退款订单号), $orderNo(商户订单号))
     * @return ResponseInterface
     * @throws InvalidArgumentException
     * @throws ReflectionException
     * @throws RuntimeException
     * @throws Throwable
     */
    public static function refundNotify(callable $callback): ResponseInterface
    {
        $app = self::getApp();
        $server = $app->getServer();
        $server->handleRefunded(function (Message $message, \Closure $next) use ($app, $callback) {
            $refundNo = $message->out_refund_no;
            $orderNo = $message->out_trade_no;
            $wechatOrderNo = $message->transaction_id;
            if ($message->refund_status == 'SUCCESS') {
                $response = $app->getClient()->get("/v3/refund/domestic/refunds/{$$refundNo}", [
                    'query' => [
                        'mchid' => $app->getMerchant()->getMerchantId(),
                    ],
                ])->toArray();

                if ($response['trade_state'] == 'SUCCESS') {
                    $callback($wechatOrderNo, $refundNo, $orderNo);
                }
            }
            return $next($message);
        });

        return $server->serve();
    }

    /**
     * 发起商家转账
     * @param string $out_batch_no
     * @param array $list
     * @param string $batch_name
     * @param string $batch_remark
     * @return void
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     *
     * @example
     *  <code>
     *      $list = [
     *           "out_detail_no": "x23zy545Bd5436",
     *           "transfer_amount": 10.23,
     *           "transfer_remark": "2020年4月报销",
     *           "openid": "o-MYE42l80oelYMDE34nYD456Xoy",
     *       ];
     *       Pay::withdrawal('批次单号', $list, '2019年1月深圳分部报销单', '2019年1月深圳分部报销单');
     *
     *      $list = [
     *          [
     *           "out_detail_no": "x23zy545Bd5436",
     *           "transfer_amount": 10.23,
     *           "transfer_remark": "2020年4月报销",
     *           "openid": "o-MYE42l80oelYMDE34nYD456Xoy",
     *          ],
     *          [
     *           "out_detail_no": "x23zy545Bd5436",
     *           "transfer_amount": 10.23,
     *           "transfer_remark": "2020年4月报销",
     *           "openid": "o-MYE42l80oelYMDE34nYD456Xoy",
     *          ]
     *      ];
     *      Pay::withdrawal('批次单号', $list, '2019年1月深圳分部报销单', '2019年1月深圳分部报销单');
     *  </code>
     */
    public static function withdrawal(string $out_batch_no, array $list, string $batch_name, string $batch_remark)
    {
        $totalAmount = 0;
        $totalNum = 0;

        if (count(array_filter($list, 'is_array')) === count($list)) {
            $transferDetails = $list;
            foreach ($list as $item) {
                $totalAmount += (int)bcmul((string)$item['transfer_amount'], '100');
                $totalNum++;
            }
        } else {
            $transferDetails = [$list];
            $totalAmount = (int)bcmul((string)$list['transfer_amount'], '100');
            $totalNum = 1;
        }

        $client = self::getApp()->getClient();

        $response = $client->postJson('/v3/transfer/batches', [
            'appid' => self::$config['appid'],
            'out_batch_no' => $out_batch_no,
            'batch_name' => $batch_name,
            'batch_remark' => $batch_remark,
            "total_amount" => $totalAmount,
            "total_num" => $totalNum,
            'transfer_detail_list' => $transferDetails,
        ])->toArray();

        if (empty($response['create_time'])) {
            $message = "操作失败";
            if (!empty($response['code']) && !empty(self::$errConfig[$response['code']])) {
                $message = self::$errConfig[$response['code']];
            }
            throw new Exception($message);
        }
    }
}